package com.lith.datalog.aspect;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Db;
import cn.hutool.json.JSONUtil;
import com.lith.datalog.annotation.DataLog;
import com.lith.datalog.handle.CompareResult;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * <p>
 * DataLog切面
 * </p>
 *
 * @author Tophua
 * @since 2020/7/15
 */
@Aspect
@Order(99)
@Component
@AllArgsConstructor
public class DataLogAspect {

    private final DataSource dataSource;

    private static final Map<Long, List<DataTem>> TEM_MAP = new ConcurrentHashMap<>();

    /**
     * <p>
     * 判断线程是否需要记录日志
     * </p>
     *
     * @param threadName threadName
     * @return boolean
     * @author Tophua
     * @since 2020/7/15
     */
    public static boolean hasThread(Long threadName) {
        return TEM_MAP.containsKey(threadName);
    }

    /**
     * <p>
     * 增加线程数据库操作
     * </p>
     *
     * @param threadName threadName
     * @param dataTem dataTem
     * @return void
     * @author Tophua
     * @since 2020/7/15
     */
    public static void put(Long threadName, DataTem dataTem) {
        if (TEM_MAP.containsKey(threadName)) {
            TEM_MAP.get(threadName).add(dataTem);
        }
    }

    /**
     * <p>
     * 切面前执行
     * </p>
     *
     * @param dataLog dataLog
     * @return void
     * @author Tophua
     * @since 2020/7/15
     */
    @SneakyThrows
    @Before("@annotation(dataLog)")
    public void before(DataLog dataLog) {
        // 获取线程名，使用线程名作为同一次操作记录
        Long threadName = Thread.currentThread().getId();
        TEM_MAP.put(threadName, new LinkedList<>());
    }

    /**
     * <p>
     * 切面后执行
     * </p>
     *
     * @param dataLog dataLog
     * @return void
     * @author Tophua
     * @since 2020/7/15
     */
    @SneakyThrows
    @After("@annotation(dataLog)")
    public void after(DataLog dataLog) {
        // 获取线程名，使用线程名作为同一次操作记录
        String threadName = Thread.currentThread().getName();
        List<DataTem> list = TEM_MAP.get(threadName);
        if (CollUtil.isEmpty(list)) {
            return;
        }
        list.forEach(dataTem -> {
            List<?> oldData = dataTem.getOldData();
            if (CollUtil.isEmpty(oldData)) {
                return;
            }
            String ids = oldData.stream().map(o -> {
                try {
                    Method method = o.getClass().getMethod("getId");
                    return method.invoke(o).toString();
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }).filter(ObjectUtil::isNotNull).collect(Collectors.joining(","));
            String sql = dataTem.getSql() + "(" + ids + ")";
            try {
                List<?> newData = Db.use(dataSource).query(sql, dataTem.getEntityType());
                dataTem.setNewData(newData);
                System.out.println("oldData:" + JSONUtil.toJsonStr(dataTem.getOldData()));
                System.out.println("newData:" + JSONUtil.toJsonStr(dataTem.getNewData()));

            } catch (SQLException e) {
                e.printStackTrace();
            }
        });
        // 异步对比存库
        this.compareAndSave(list);
        // 移除当前线程
        TEM_MAP.remove(threadName);
    }

    /**
     * <p>
     * 对比保存
     * </p>
     *
     * @param list list
     * @return void
     * @author Tophua
     * @since 2020/7/15
     */
    @Async
    public void compareAndSave(List<DataTem> list) {
        StringBuilder sb = new StringBuilder();
        list.forEach(dataTem -> {
            List<?> oldData = dataTem.getOldData();
            List<?> newData = dataTem.getNewData();
            // 按id排序
            oldData.sort(Comparator.comparingLong(d -> {
                try {
                    Method method = d.getClass().getMethod("getId");
                    return Long.parseLong(method.invoke(d).toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return 0L;
            }));
            newData.sort(Comparator.comparingLong(d -> {
                try {
                    Method method = d.getClass().getMethod("getId");
                    return Long.parseLong(method.invoke(d).toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return 0L;
            }));

            for (int i = 0; i < oldData.size(); i++) {
                final int[] finalI = {0};
                sameClazzDiff(oldData.get(i), newData.get(i)).forEach(r -> {
                    if (finalI[0] == 0) {
                        sb.append(StrUtil.LF);
                        sb.append(StrUtil.format("修改表：【{}】", dataTem.getTableName()));
                        sb.append(StrUtil.format("id：【{}】", r.getId()));
                    }
                    sb.append(StrUtil.LF);
                    sb.append(StrUtil.format("把字段[{}]从[{}]改为[{}]", r.getFieldName(), r.getOldValue(), r.getNewValue()));
                    finalI[0]++;
                });
            }
        });
        if (sb.length() > 0) {
            sb.deleteCharAt(0);
        }
        // 存库
        System.err.println(sb.toString());
    }

    /**
     * <p>
     * 相同类对比
     * </p>
     *
     * @param obj1 obj1
     * @param obj2 obj2
     * @return java.util.List<com.lith.datalog.handle.CompareResult>
     * @author Tophua
     * @since 2020/7/15
     */
    private List<CompareResult> sameClazzDiff(Object obj1, Object obj2) {
        List<CompareResult> results = new ArrayList<>();
        Field[] obj1Fields = obj1.getClass().getDeclaredFields();
        Field[] obj2Fields = obj2.getClass().getDeclaredFields();
        Long id = null;
        for (int i = 0; i < obj1Fields.length; i++) {
            obj1Fields[i].setAccessible(true);
            obj2Fields[i].setAccessible(true);
            Field field = obj1Fields[i];
            try {
                Object value1 = obj1Fields[i].get(obj1);
                Object value2 = obj2Fields[i].get(obj2);
                if ("id".equals(field.getName())) {
                    id = Long.parseLong(value1.toString());
                }
                if (!ObjectUtil.equal(value1, value2)) {
                    CompareResult r = new CompareResult();
                    r.setId(id);
                    r.setFieldName(field.getName());
                    // 获取注释
                    r.setFieldComment(field.getName());
                    r.setOldValue(value1);
                    r.setNewValue(value2);
                    results.add(r);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return results;
    }

}
